﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using IInject.Expressions;
using IInject.Internal;

namespace IInject.Registration
{
	///<summary>
	/// A class that provides methods for building
	/// and registering <see cref="RegistryContext"/>s.
	///</summary>
	public class DefaultContextBuilder : DisposableContainerMember, IContextBuilder
	{
		#region Fields

		private readonly IRegistryProvider _registry;
		private readonly List<InjectPropertyContext> _properties = new List<InjectPropertyContext>();

		private InjectionExpressionVisitor _expressionVisitor;

		#endregion

		#region Constructors

		/// <summary>
		/// Initializes a new instance of the <see cref="DefaultContextBuilder"/>
		/// using the specified <see cref="IContainer"/>.
		/// </summary>
		/// <param name="container"></param>
		internal protected DefaultContextBuilder(IContainer container) : base(container)
		{
			_registry = container.Registry;
		}

		///<summary>
		/// Initializes a new instance of the <see cref="DefaultContextBuilder"/>
		/// using the specified <see cref="Type"/> and <see cref="IContainer"/>.
		///</summary>
		///<param name="from">The type used to register the <see cref="RegistryContext"/>.</param>
		///<param name="container">The <see cref="IContainer"/></param>
		public DefaultContextBuilder(Type from, IContainer container) : this(container)
		{
			FromType = from;
		}

		#endregion

		#region Properties

		/// <summary>
		/// Gets or sets the key to use when registering and resolving the <see cref="RegistryContext"/>.
		/// </summary>
		protected string Key { get; set; }

		/// <summary>
		/// Gets or sets the <see cref="Scope"/> associated with the instance of the resolved type.
		/// </summary>
		protected Scope Scope { get; set; }

		/// <summary>
		/// Gets or sets the instance to return when resolved.
		/// </summary>
		protected object Instance { get; set; }

		/// <summary>
		/// Gets or sets the type used to register the <see cref="RegistryContext"/>.
		/// </summary>
		protected Type FromType { get; private set; }

		/// <summary>
		/// Gets or sets the type the <see cref="IContainer"/> will return when resolved.
		/// </summary>
		protected Type ToType { get; set; }

		/// <summary>
		/// Returns true if the <see cref="RegistryContext"/> has been
		/// registered with the <see cref="IContainer"/>, false otherwise.
		/// </summary>
		protected bool HasRegistered { get; private set; }

		/// <summary>
		/// The properties to be injected when the type is resolved.
		/// </summary>
		protected List<InjectPropertyContext> Properties
		{
			get
			{
				return _properties;
			}
		} 

		///<summary>
		/// Gets the <see cref="IRegistryProvider"/> responsible for registering all types.
		///</summary>
		protected IRegistryProvider Registry
		{
			get
			{
				return _registry;
			}
		}

		/// <summary>
		/// Visits the expressions for properties that are to be injected.
		/// </summary>
		protected InjectionExpressionVisitor ExpressionVisitor
		{
			get
			{
				if (_expressionVisitor == null)
				{
					_expressionVisitor = new InjectionExpressionVisitor(_properties);
				}

				return _expressionVisitor;
			}
		}


		#endregion

		#region Methods

		///<summary>
		/// Registers the current configuration with the <see cref="Container"/>.
		///</summary>
		///<returns>The current <see cref="IContainer"/>.</returns>
		public IContainer Apply()
		{
			if (!HasRegistered)
			{
				ApplyCore();
				HasRegistered = true;
			}

			return Container;
		}

		///<summary>
		/// Adds the type used to register the <see cref="RegistryContext"/>.
		///</summary>
		///<typeparam name="T">The type used to register the <see cref="RegistryContext"/>.</typeparam>
		///<returns>The current <see cref="IContextBuilder"/>.</returns>
		public virtual IContextBuilder<T> From<T>()
		{
			return (IContextBuilder<T>)From(typeof(T));
		}

		///<summary>
		/// Adds the type used to register the <see cref="RegistryContext"/>.
		///</summary>
		///<param name="from">The type used to register the <see cref="RegistryContext"/>.</param>
		///<returns>The current <see cref="IContextBuilder"/>.</returns>
		public virtual IContextBuilder From(Type from)
		{
			if (from != null)
			{
				FromType = from;
			}

			return this;
		}

		///<summary>
		/// Adds the type the <see cref="IContainer"/> will return when resolved.
		///</summary>
		///<param name="toType">The type the <see cref="IContainer"/> will return when resolved.</param>
		///<returns>The current <see cref="IContextBuilder"/>.</returns>
		public virtual IContextBuilder To(Type toType)
		{
			if (FromType.IsAssignableFrom(toType))
			{
				ToType = toType;
			}

			return this;
		}

		///<summary>
		/// Adds a key to use when registering the <see cref="RegistryContext"/>.
		///</summary>
		///<param name="key">The key to use when registering the <see cref="RegistryContext"/>.</param>
		///<returns>The current <see cref="IContextBuilder"/>.</returns>
		public virtual IContextBuilder With(string key)
		{
			Key = key;
			return this;
		}

		///<summary>
		/// Adds the <see cref="Scope"/> associated with the instance of the resolved type.
		///</summary>
		///<param name="scope">The <see cref="Scope"/> associated with the instance of the resolved type.</param>
		///<returns>The current <see cref="IContextBuilder"/>.</returns>
		public virtual IContextBuilder With(Scope scope)
		{
			Scope = scope;
			return this;
		}

		///<summary>
		/// The instance to return when resolved.
		///</summary>
		///<param name="instance">The instance to return when resolved.</param>
		///<typeparam name="T">The type of object to return.</typeparam>
		///<returns>The current <see cref="IContextBuilder"/>.</returns>
		public virtual IContextBuilder Return<T>(T instance)
		{
			if (!Equals(instance, default(T)))
			{
				var type = instance.GetType();

				if (FromType.IsAssignableFrom(type))
				{
					ToType = type;
					Instance = instance;
					With(Scope.Singleton);
				}
			}

			return this;
		}

		/// <summary>
		/// Selects a property and injects that property when resolved from the registry.
		/// </summary>
		/// <param name="propExpression">The expression used to get the property to inject.</param>
		///<returns>The current <see cref="IContextBuilder"/>.</returns>
		public virtual IContextBuilder Inject<T>(Expression<Func<T, object>> propExpression)
		{
			VisitExpression(propExpression);

			return this;
		}

		/// <summary>
		/// The method that registers the <see cref="RegistryContext"/>.
		/// </summary>
		protected virtual void ApplyCore()
		{
			Registry.Register(FromType, Build());
		}

		///<summary>
		/// Builds the current configuration into a <see cref="RegistryContext"/>.
		///</summary>
		///<returns>The built <see cref="RegistryContext"/>.</returns>
		protected virtual RegistryContext Build()
		{
			var context = new RegistryContext
			{
				Key = Key,
				Scope = Scope,
				FromType = FromType,
				ToType = ToType ?? FromType,
				Instance = Instance,
			};

			if (_properties.Count > 0)
			{
				context.Properties.AddRange(Properties);
			}

			return context;
		}

		protected virtual void VisitExpression(Expression propExpression)
		{
			if (propExpression != null)
			{
				ExpressionVisitor.Visit(propExpression);
			}
		}

		/// <summary>
		/// When overridden in a derived class, disposes of this instances' allocated resources.
		/// </summary>
		protected override void Dispose(bool disposing)
		{
			Apply();
		}

		#endregion
	}
}
